Java 锁介绍

JVM中的锁

        每个对象都有一个对象头mark word标志信息,主要存储:对象hashcode值、gc年龄、偏向锁标识、锁标识等

        在使用synchronized关键字加锁时,根据竞争情况,会出现锁的一个升级过程。

        偏向锁(无竞争单线程时) -> 轻量级锁(多个线程竞争低并发) -> 重量级锁(高并发)

        未获取轻量级锁的其他线程可以进行短暂的自旋等待锁释放,如果短暂自旋以后未能获取到会升级为重量级锁。

     偏向锁

            偏向锁是一个线程获取锁以后设置对象头偏向锁id指向该线程,当该线程再次获取该锁, 不需要做monitor等判断, 只需要通过cas判断判断对象头偏向锁字段存储的线程id是否是本线程id,如果是则可以直接获取, 性能较高.
            偏向锁使用只在资源不存在或者存在十分小的竞争情况下使用,能有效提高性能,
            在资源竞争激烈(多线程)的情况下, 每次线程都试图获取偏向锁,但是每次都获取不到, 所以性能下降.
            偏向锁在jdk1.6开始默认启用, 参数为:-XX:+UseBiasedLocking  -XX:BiasedLockingStartupDelay=n    为偏向锁启用时间, 默认会在jvm启动几秒后启用, jvm团队认为 jvm刚启动时, 资源竞争高,所以会在后续几秒后启用.

            设计使用场景:单线程使用同步块代码时进一步提高性能。相比轻量级锁执行多次cas操作,偏向锁只执行一次cas操作对象头的偏向锁id(出现竞争撤销时,执行一次撤销操作)

        轻量级锁(BasicObjectLock)

            嵌入在线程栈中的对象BasicObjectLock有(BasicLock)对象头和指向持有该锁的对象指针组成.
            普通的锁性能不理想, 轻量级锁是一种快速锁定方法.
            使用轻量级锁, 在判断是否一个线程持有对象锁, 只需要通过cas操作判断对象头字段的轻量级锁指针是否指向该线程栈即可. 与偏向锁的区别是在无竞争情况下偏向锁只需执行一次cas操作置换偏向锁threadId(出现竞争时还有一次撤销操作),轻量级锁需要多次cas锁定与释放。
            
            轻量级锁失败,表示存在竞争, 升级为重量级锁(常规锁)
            在没有锁竞争情况下, 减少传统互斥量产生的性能消耗
            在激烈竞争时, 轻量级锁会多做额外操作, 导致性能下降

            设计使用场景:多线程交替执行同步代码块(同步代码块执行时间不能太长)时提高性能,当存在同时使用时就会升级为重量级锁。

           轻量级锁获取失败时会根据情况进行自旋,这个过程就是自旋锁

            在竞争存在时, 如果线程可以很快获得锁, 可以不在OS(操作系统)层挂起线程(性能损耗高), 可以多几个空操作,这就叫自旋。比如当获取轻量级锁失败时,会进行短暂的自旋,如果还是获取不到就升级为重量解锁。
            自旋锁jdk1.6 -XX:+UseSpinning    开启自旋锁, jdk 1.7中默认内置实现了自旋锁, 参数废弃.
            如果同步块很长,自选失败, 会降低系统性能
            如果同步块很短, 自旋成功, 节省挂起时的切换时间,提升性能

            设计应用场景:当同步代码块执行迅速时,等待锁的线程自旋一段短暂的时间就可以获取到锁,可以避免系统上下文切换的消耗,不过自旋需要占用一些cpu时间。
            
        偏向锁/轻量级锁/自旋锁不是java语言层锁优化方法,是JVM中的锁的优化方法基本步骤如下:
               1. 偏向锁可用会先尝试偏向锁
               2. 轻量级锁可用会先尝试轻量级锁
               3. 以上失败,会尝试自旋锁
               4. 再失败, 尝试普通锁, 使用OS互斥量在操作系统层线程挂起

Java语言层锁优化

        1. 减少锁持有时间(尽量再最小的区域内使用锁(synchronized)).

                就是只对需要同步块那部分代码使用同步块, 不必要代码不要放到同步块中

        2. 减小锁的粒度

                将需要锁定的大对象拆成小对象, 增加并行度, 较少锁竞争, 这样以来就可以增加偏向锁和轻量级锁的成功率
                该种实现例子就是ConcurrentHashMap
                HashMap实现同步可以通过Collections.synchronizedMap(map)获得一个同步SynchronizedMap,
                SynchronizedMap的方法进行了同步块实现
                ConcurrentHashMap的实现就是减小锁粒度
                        将内容分为若个segment片, 每个segment, 存储的k-v具体值.
                        在put操作时,先锁定某个segment块在进行put操作, 而不是锁定整个map.
                        减小锁粒度以后, ConcurrentHashMap就可以允许多个线程同时进入操作,只要多个线程
                        操作的是不同的segment.(segment锁实现在jdk1.7及之前,1.8之后使用cas原语重新实现了)

        3.锁分离

                根据功能分离锁
                如读写锁ReadWriteLock,在读多写少情况时, 能提高性能.
                允许多读,但是只能一个写, 只要有一个线程获取读锁,其他读线程可读.写线程等待
                在有一个线程写时, 其他读线程和写线程等待.               

                LinkedBlockingQueue  链表队列 头take,尾put, 头尾操作互不影响,

                可以一个线程put,一个take, 也是一种锁分离的应用

        4. 锁粗化

                通常情况下, 保证并发多线程有效, 要求每个线程持有锁的时间尽量少, 以便尽早释放锁, 其他线程得以获得.
                但是,如果对同一个锁频繁请求和释放过多也会导致资源消耗, 此时需要对锁进行粗化.
                比如: 下面的锁需要粗化, 放大同步块区域(synchronized时可重入锁)
                    public void method() {
                        synchronized(lock) {do ..}
                        synchronized(lock) {do ..}
                    }
                    public void method() {
                        for (x...) {
                            synchronized(lock) {do ..}
                        }
                    }

        5. 锁消除

                方法内不可能被多线程使用的变量, 但是出现了加锁, 此时编译器发现对象不可能被共享, 将会消除这些锁操作.
                例如: StringBuffer 是个线程安全的方法都有同步块, 但是此处不需要加锁, 编译器编译时会去掉锁操作
                    public String method(String s) {
                        StringBuffer sb = new StringBuffer();
                        sb.append(s);
                        return sb.toString();
                    }
                锁消除的JVM参数

                -XX:+DoEscapeAnalysis    逃逸分析, 锁对象是否逃逸(一个对象如果在方法内创建被使用, 但是此后方法外也有使用, 被称为逃逸)

                -XX:+EliminateLocks        启用锁消除 可以对上面方法循环2000000次计算耗时

        6.无锁

                锁是悲观操作, 悲观操作认为操作是多线程不安全的,存在竞争 ,所以使用锁
                无锁就是乐观操作, 乐观操作认为所有操作都是安全的不存在竞争关系
                无锁的实现方式      :CAS

CAS(Compare And Swap) 比较交换, 进来就比较并交换值, 不等待, 成功继续执行,否则重试或者放弃.
CAS有三个参数V(variable), E(expected), N(new),分别是V需要操作的变量, E期望变量的值, N操作后的新值, 
交换过程为变量V的值与预期值E相等, 则将变量V赋值为N新值. if (v == e) v = n;
CAS是cpu的一条指令, 能保证线程安全, 该种性能远远高于锁机制。通过该方式应用在具体实现中, 判断是否多线程干扰, 如果有就重试.
(j.u.c包)java.util.concurrent.atomic包内存在很多原子操作的对象就是采用CAS如:AtomicInteger

参考:   对象头-锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值